package cn.lzy.study.ratelimiter.aspect;

import cn.lzy.study.ratelimiter.annotation.RateLimiter;
import cn.lzy.study.ratelimiter.config.RedisConfig;
import cn.lzy.study.ratelimiter.enums.LimiterType;
import cn.lzy.study.ratelimiter.exception.RateLimiterException;
import cn.lzy.study.ratelimiter.utils.IpUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Collections;

/**
 * @author LZY
 * @since 2022-08-17
 */
@Aspect
@Component
public class RateLimiterAspect {

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;
    @Autowired
    private RedisScript<Long> redisScript;


    private static final Logger logger = LoggerFactory.getLogger(RateLimiterAspect.class);


    @Before("@annotation(rateLimiter)")
    public void before(JoinPoint joinPoint, RateLimiter rateLimiter) throws RateLimiterException {
        int count = rateLimiter.count();
        int time = rateLimiter.time();
        // 获取key值
        String key = getCombineKey(joinPoint, rateLimiter);
        // 获取访问次数
        try {
            // redisTemplate.execute(需执行的脚本, 键值对集合, 参数数组)，执行的脚本里，获取的key将从键值对集合中获取，time和count则从参数数组中获取
            Long number = redisTemplate.execute(redisScript, Collections.singletonList(key), time, count);
            if (number == null || number.intValue() > count) {  // 访问次数
                // 超过限流阈值
                logger.info("当前接口已达到最大限流次数！");
                throw new RateLimiterException("访问过于频繁，请稍后访问！");
            }
            logger.info("一个时间窗内限制访问次数：{}，当前访问次数：{}，缓存的key：{}", count, number, key);
        } catch (Exception e) {
            throw e;
        }
    }


    /**
     * 获取结合key
     * 该key作用是 接口调用次数缓存在Redis的key
     * 【存储到Redis的key格式】
     * 默认限制： rate_limiter:cn.lzy.study.ratelimiter.controller.LoginController-login
     * IP限制：  rate_limiter:192.168.1.123-cn.lzy.study.ratelimiter.controller.LoginController-login
     * @param joinPoint
     * @param rateLimiter
     * @return
     */
    private String getCombineKey(JoinPoint joinPoint, RateLimiter rateLimiter) {
        StringBuffer key = new StringBuffer(rateLimiter.key());
        if (rateLimiter.limiterType() == LimiterType.IP) {
            // 获取请求ip地址
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String ipAddr = IpUtils.getIpAddr(request);
            key.append(ipAddr).append("-");
        }
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        key.append(method.getDeclaringClass())
                .append("-")
                .append(method.getName());
        return key.toString();
    }
}
